﻿#include "World.h"

#include "App/Window.h"
#include "Render/ColorRenderPass.h"
#include "World/Chunk.h"
#include "Scene/Level.h"
#include "Util/Random.h"
#include "Util/Math.h"
#include "Asset/AssetManager.h"
#include "Scene/Skybox.h"

World::World(Level &level, const Ref<Persistence> &persistence, std::vector<Ref<WorldBehavior>> behaviors)
    : m_persistence(persistence),
      m_behaviors(std::move(behaviors)),
      m_level(level)
{
  m_worldGenerator = CreateRef<WorldGenerator>(m_persistence->GetSeed());

  // 用于渲染不透明物体
  m_opaqueShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/OpaqueShader");
  // 用于渲染透明物体
  m_transparentShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/TransparentShader");
  // 用于混合透明物体和不透明物体
  m_blendShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/BlendShader");

  const int atlasSize = 16 * 64;
  m_atlas = AssetManager::LoadAsset<Atlas>("assets/textures/block_atlas.json");
  m_atlas->SetFilterParam(GL_NEAREST, GL_NEAREST);
}

Ref<Chunk> World::LoadChunk(const glm::ivec2 &worldPosition)
{
  LOG_TRACE("Load chunk: ({}, {})", worldPosition.x, worldPosition.y);
  Ref<Chunk> chunk = nullptr;

  if (IsChunkLoaded(worldPosition))
  {
    chunk = chunks[worldPosition];
  }
  else
  {
    chunk = m_persistence->GetChunk(worldPosition);
    if (!chunk)
    {
      chunk = CreateRef<Chunk>(worldPosition);
      m_persistence->CommitChunk(chunk);
      m_worldGenerator->GenerateChunk(chunk);
    }

    chunks[worldPosition] = chunk;

    // 相邻已加载的区块设置脏标记
    std::array<glm::ivec2, 4> chunksAround = {{{0, 16}, {16, 0}, {0, -16}, {-16, 0}}};
    for (const glm::ivec2 &offset : chunksAround)
    {
      glm::ivec2 neighborPosition = worldPosition + offset;

      if (!IsChunkLoaded(neighborPosition))
        continue;

      chunks[neighborPosition]->SetDirty();
    }

    for (int x = 0; x < Chunk::HorizontalSize; x++)
    {
      for (int y = 0; y < Chunk::VerticalSize; y++)
      {
        for (int z = 0; z < Chunk::HorizontalSize; z++)
        {
          glm::ivec3 blockPos = {x, y, z};

          for (auto &behavior : m_behaviors)
          {
            behavior->OnBlockPlace(nullptr, 
              blockPos + glm::ivec3(worldPosition.x, 0, worldPosition.y), 
              chunk->GetBlockAt(blockPos), *this);
          }
        }
      }
    }

    for (auto &behavior : m_behaviors)
    {
      behavior->OnChunkLoaded(chunk, *this);
    }
  }

  return chunk;
}

Ref<Chunk> World::GetChunk(const glm::ivec2 &worldPosition) const
{
  if (IsChunkLoaded(worldPosition))
    return chunks.at(worldPosition);

  return nullptr;
}

void World::UnloadChunk(const Ref<Chunk> &chunk)
{
  LOG_TRACE("Unload chunk: ({}, {})", chunk->GetPosition().x, chunk->GetPosition().y);

  const auto chunkPos = chunk->GetPosition();
  chunks.erase(chunkPos);
}

BlockData *World::GetBlockAt(const glm::ivec3 &worldPosition)
{
  return LoadChunk(GetChunkIndex(worldPosition))->GetBlockAt(World::ToPositionInChunk(worldPosition));
}

BlockData *World::GetBlockAtIfLoaded(const glm::ivec3 &worldPosition) const
{
  const glm::ivec2 index = GetChunkIndex(worldPosition);
  if (!IsChunkLoaded(index))
  {
    return nullptr;
  }

  return chunks.at(index)->GetBlockAt(World::ToPositionInChunk(worldPosition));
}

bool World::IsChunkLoaded(const glm::ivec2 &worldPosition) const
{
  return chunks.contains(worldPosition);
}

glm::ivec2 World::GetChunkIndex(const glm::ivec3 &worldPosition)
{
  return {
      worldPosition.x - Util::PositiveMod(worldPosition.x, Chunk::HorizontalSize),
      worldPosition.z - Util::PositiveMod(worldPosition.z, Chunk::HorizontalSize),
  };
}

void World::SetViewDistance(int distance)
{
  if (distance <= 0)
    return;

  int old = m_viewDistance;
  m_viewDistance = distance;

  if (old != m_viewDistance)
  {
    LOG_INFO("Set view distance to {}", m_viewDistance);

    if (old > m_viewDistance)
    {
      for (int i = old; i > m_viewDistance; i--)
      {
        for (int j = old; j > m_viewDistance; j--)
        {
          glm::ivec2 position = glm::ivec2(i * 16, j * 16);
          if (IsChunkLoaded(position))
          {
            UnloadChunk(chunks[position]);
          }
        }
      }
    }
  }
}

bool World::PlaceBlock(const glm::ivec3 &worldPosition, BlockData blockData, Player *player)
{
  const glm::ivec3 positionInChunk = ToPositionInChunk(worldPosition);
  const glm::ivec2 chunkIndex = GetChunkIndex(worldPosition);
  const Ref<Chunk> chunk = LoadChunk(chunkIndex);
  const BlockData *oldBlock = GetBlockAt(worldPosition);

  for (auto &behavior : m_behaviors)
  {
    behavior->OnBlockBreak(player, worldPosition, oldBlock, *this);
  }

  chunk->PlaceBlock(positionInChunk, blockData);

  for (auto &behavior : m_behaviors)
  {
    behavior->OnBlockPlace(player, worldPosition, &blockData, *this);
  }

  constexpr std::array<glm::ivec3, 6> blocksAround = {
      {{0, 0, 1}, {1, 0, 0}, {0, 0, -1}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}}};

  for (const glm::ivec3 &offset : blocksAround)
  {
    const glm::ivec3 neighbor = positionInChunk + offset;
    const glm::ivec3 neighborWorldPosition = worldPosition + offset;
    if (!Chunk::IsValidPosition({neighbor.x, neighbor.y, neighbor.z}))
    {
      const auto &chunk = LoadChunk(GetChunkIndex(neighborWorldPosition));
      chunk->SetDirty();
    }
  }
  return false;
}

/**
 * @brief 根据玩家位置排序
 */
void World::SortChunkIndices(const glm::vec3 &playerPos, const Ref<ChunkPositions> &index)
{
  index->clear();
  if (index->capacity() < chunks.size())
  {
    index->reserve(chunks.size());
  }

  glm::vec2 playerXZ = glm::vec2(playerPos.x, playerPos.z);
  for (const auto &[key, value] : chunks)
  {
    index->emplace_back(key, value->Distance2DTo(playerXZ));
  }

  std::sort(index->begin(), index->end(),
            [](const auto &a, const auto &b)
            { return b.second < a.second; });
}

void World::RebuildChunkMeshes(const Ref<ChunkPositions> &chunkIndices)
{
  int rebuildCount = 0;

  for (auto &index : std::ranges::reverse_view(*chunkIndices))
  {
    if (rebuildCount >= MaxRebuildsAllowedPerFrame)
      break;
    const auto &chunk = chunks[index.first];
    if (chunk && chunk->NeedsRebuildMesh())
    {
      chunk->SetAtlas(m_atlas);
      chunk->RebuildMesh(*this);
      rebuildCount++;
    }
  }
}

glm::ivec3 World::ToPositionInChunk(const glm::ivec3 &worldPosition)
{
  return {
      Util::PositiveMod(worldPosition.x, Chunk::HorizontalSize),
      worldPosition.y,
      Util::PositiveMod(worldPosition.z, Chunk::HorizontalSize),
  };
}

void World::Update(const glm::vec3 &playerPosition, float deltaTime)
{
  textureAnimation += deltaTime * TextureAnimationSpeed;

  glm::vec2 playerChunkPosition = GetChunkIndex(playerPosition);

  auto chunksCopy = chunks;
  float unloadDistance = static_cast<float>(m_viewDistance + 1) * 16 + 8.0f;
  for (const auto &[chunkPosition, chunk] : chunksCopy)
  {
    if (glm::abs(glm::distance(glm::vec2(chunkPosition), playerChunkPosition)) > unloadDistance)
    {
      UnloadChunk(chunk);
    }
  }

  float loadDistance = static_cast<float>(m_viewDistance) * 16 + 8.0f;
  for (int32_t i = -m_viewDistance; i <= m_viewDistance; i++)
  {
    for (int32_t j = -m_viewDistance; j <= m_viewDistance; j++)
    {
      glm::ivec2 position = glm::ivec2(i * 16, j * 16) + glm::ivec2(playerChunkPosition);
      if (IsChunkLoaded(position))
      {
        continue;
      }

      float distance = glm::abs(glm::distance(glm::vec2(position), playerChunkPosition));
      if (distance <= loadDistance)
      {
        LoadChunk(position);
      }
    }
  }

  for (auto &behavior : m_behaviors)
  {
    behavior->Update(deltaTime);
  }
}

void World::RenderOpaque(const Ref<Camera> &camera, const glm::vec3 &playerPos)
{
  static auto sortedChunkIndices = CreateRef<ChunkPositions>();
  SortChunkIndices(playerPos, sortedChunkIndices);
  RebuildChunkMeshes(sortedChunkIndices);

  m_opaqueShader->SetFloat("uFrame", textureAnimation);
  m_opaqueShader->SetTexture("uTexture", m_atlas);

  for (const auto index : *sortedChunkIndices)
  {
    const auto &chunk = chunks[index.first];
    chunk->SetShader(m_opaqueShader);
    chunk->RenderOpaque(camera);
    chunk->RenderSemiTransparent(camera);
  }

  Frustum frustum(camera->GetViewProjectionMatrix());
  for (auto &behavior : m_behaviors)
  {
    behavior->RenderOpaque(camera->GetViewProjectionMatrix(), playerPos, frustum);
  }
}

void World::RenderDeferredLight(const Ref<Camera> &camera, glm::vec3 playerPos, Skybox &skybox, Ref<FrameBuffer> blendRender, Ref<FrameBuffer> opaqueRender)
{
  static auto defferrShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/DeferredLight");
  ColorRenderPass renderPass(defferrShader);
  renderPass.SetTexture("gAlbedoSpec", blendRender->GetColorAttachment(0), 0);
  renderPass.SetTexture("gPosition", opaqueRender->GetColorAttachment(1), 1);
  renderPass.SetTexture("gNormal", opaqueRender->GetColorAttachment(2), 2);
  renderPass.SetTexture("gVerticeLight", opaqueRender->GetColorAttachment(3), 3);

  defferrShader->SetFloat("uTime", skybox.GetTimeOfDay());
  defferrShader->SetVec3("light.direction", skybox.GetSunshineDirection());
  defferrShader->SetVec4("light.color", skybox.GetSunshineColorByTime());
  renderPass.Render();
}

void World::RenderDeferredLightTransparent(const Ref<Camera> &camera, glm::vec3 playerPos, Skybox &skybox, Ref<FrameBuffer> blendRender, Ref<FrameBuffer> opaqueRender)
{
  static auto defferrShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/DeferredLightTransparent");
  ColorRenderPass renderPass(defferrShader);
  renderPass.SetTexture("gAlbedoSpec", blendRender->GetColorAttachment(0), 0);
  renderPass.SetTexture("gPosition", opaqueRender->GetColorAttachment(1), 1);
  renderPass.SetTexture("gNormal", opaqueRender->GetColorAttachment(2), 2);

  defferrShader->SetFloat("uTime", skybox.GetTimeOfDay());
  defferrShader->SetVec3("light.direction", skybox.GetSunshineDirection());
  defferrShader->SetVec4("light.color", skybox.GetSunshineColorByTime());
  renderPass.Render();
}

void World::RenderTransparent(const Ref<Camera> &camera, glm::vec3 playerPos, Ref<FrameBuffer> lightedRender, Ref<FrameBuffer> opaqueRender)
{
  auto width = opaqueRender->GetWidth();
  auto height = opaqueRender->GetHeight();

  static auto sortedChunkIndices = CreateRef<ChunkPositions>();
  SortChunkIndices(playerPos, sortedChunkIndices);
  RebuildChunkMeshes(sortedChunkIndices);

  // 绘制透明物体需要深度测试
  static const float zNear = 0.1f;
  static const float zFar = 1000.0f;
  m_transparentShader->SetFloat("zNear", zNear);
  m_transparentShader->SetFloat("zFar", zFar);
  m_transparentShader->SetFloat("uFrame", textureAnimation);
  m_transparentShader->SetTexture("uTexture", m_atlas, 0);
  m_transparentShader->SetTexture("uOpaqueDepth", opaqueRender->GetDepthAttachment(), 1);
  m_transparentShader->Bind();

  for (const auto &[key, chunk] : chunks)
  {
    chunk->SetShader(m_transparentShader);
    chunk->RenderSemiTransparent(camera);
  }
}
